--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / frontend / js / Codec2Loader.js
Displaying Raw • Download
meshchatx/src/frontend/js/Codec2Loader.js 9f75e3db181925ddd253f7fb231af81f9fb8c013 (9f75e3db) Text, 6.10 KB
Tff7b72const Te6edf3codec2ScriptPaths Tff7b72= Tb4b4b4[
Ta5d6ff"/assets/js/codec2-emscripten/c2enc.js"Tb4b4b4,
Ta5d6ff"/assets/js/codec2-emscripten/c2dec.js"Tb4b4b4,
Ta5d6ff"/assets/js/codec2-emscripten/sox.js"Tb4b4b4,
Ta5d6ff"/assets/js/codec2-emscripten/codec2-lib.js"Tb4b4b4,
Ta5d6ff"/assets/js/codec2-emscripten/wav-encoder.js"Tb4b4b4,
Ta5d6ff"/assets/js/codec2-emscripten/codec2-microphone-recorder.js"Tb4b4b4,
Tb4b4b4]Tb4b4b4;
Tff7b72let Te6edf3loadPromise Tff7b72= Tff7b72nullTb4b4b4;
Tff7b72let Te6edf3resolvedOk Tff7b72= Tff7b72falseTb4b4b4;
Tff7b72let Te6edf3integrityHashes Tff7b72= Tff7b72nullTb4b4b4;
T8b949e/** Computes SHA-384 hash of ArrayBuffer for SRI verification. */
Tff7b72async Tff7b72function Te6edf3computeSriHashTb4b4b4(Te6edf3bufTb4b4b4) Tb4b4b4{
Tff7b72const Te6edf3hash Tff7b72= Tff7b72await Te6edf3cryptoTb4b4b4.Te6edf3subtleTb4b4b4.Te6edf3digestTb4b4b4(Ta5d6ff"SHA-384"Tb4b4b4, Te6edf3bufTb4b4b4)Tb4b4b4;
Tff7b72const Te6edf3base64 Tff7b72= Te6edf3btoaTb4b4b4(Tffa657StringTb4b4b4.Te6edf3fromCharCodeTb4b4b4(Tb4b4b4...Tff7b72new Tffa657Uint8ArrayTb4b4b4(Te6edf3hashTb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tff7b72return Ta5d6ff`Ta5d6ffsha384-Tffd700${Te6edf3base64Tffd700}Ta5d6ff`Tb4b4b4;
Tb4b4b4}
T8b949e/** Loads integrity.json. Returns null if not available. */
Tff7b72async Tff7b72function Te6edf3loadIntegrityHashesTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3integrityHashes Tff7b72!== Tff7b72nullTb4b4b4) Tff7b72return Te6edf3integrityHashesTb4b4b4;
Tff7b72try Tb4b4b4{
Tff7b72const Te6edf3res Tff7b72= Tff7b72await Te6edf3fetchTb4b4b4(Ta5d6ff"/assets/js/codec2-emscripten/integrity.json"Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3resTb4b4b4.Te6edf3okTb4b4b4) Tff7b72return Tff7b72nullTb4b4b4;
Tff7b72const Te6edf3data Tff7b72= Tff7b72await Te6edf3resTb4b4b4.Te6edf3jsonTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3integrityHashes Tff7b72= Te6edf3dataTb4b4b4.Te6edf3files Tff7b72|| Tb4b4b4{Tb4b4b4}Tb4b4b4;
Tff7b72return Te6edf3integrityHashesTb4b4b4;
Tb4b4b4} Tff7b72catch Tb4b4b4{
Tff7b72return Tff7b72nullTb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e/** Verifies SRI hash. Throws if mismatch or missing when required. */
Tff7b72async Tff7b72function Te6edf3verifySriTb4b4b4(Te6edf3bufTb4b4b4, Te6edf3expectedHashTb4b4b4, Te6edf3nameTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3expectedHashTb4b4b4) Tb4b4b4{
Tff7b72throw Tff7b72new Tf85149ErrorTb4b4b4(Ta5d6ff`Ta5d6ffCodec2: SRI hash missing for Tffd700${Te6edf3nameTffd700}Ta5d6ff. Refusing to load untrusted code.Ta5d6ff`Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72const Te6edf3actualHash Tff7b72= Tff7b72await Te6edf3computeSriHashTb4b4b4(Te6edf3bufTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3actualHash Tff7b72!== Te6edf3expectedHashTb4b4b4) Tb4b4b4{
Tff7b72throw Tff7b72new Tf85149ErrorTb4b4b4(Ta5d6ff`Ta5d6ffCodec2: SRI hash mismatch for Tffd700${Te6edf3nameTffd700}Ta5d6ff. Possible tampering detected.Ta5d6ff`Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72async Tff7b72function Te6edf3injectScriptTb4b4b4(Te6edf3srcTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72typeof Tffa657document Tff7b72=== Ta5d6ff"undefined"Tb4b4b4) Tb4b4b4{
Tff7b72return Tffa657PromiseTb4b4b4.Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72const Te6edf3attrName Tff7b72= Ta5d6ff"data-codec2-src"Tb4b4b4;
Tff7b72const Te6edf3loadedAttr Tff7b72= Ta5d6ff"data-codec2-loaded"Tb4b4b4;
Tff7b72const Te6edf3existing Tff7b72= Tffa657documentTb4b4b4.Te6edf3querySelectorTb4b4b4(Ta5d6ff`Ta5d6ffscript[Tffd700${Te6edf3attrNameTffd700}Ta5d6ff="Tffd700${Te6edf3srcTffd700}Ta5d6ff"]Ta5d6ff`Tb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3existingTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3existingTb4b4b4.Te6edf3getAttributeTb4b4b4(Te6edf3loadedAttrTb4b4b4) Tff7b72=== Ta5d6ff"true"Tb4b4b4) Tb4b4b4{
Tff7b72return Tffa657PromiseTb4b4b4.Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72return Tff7b72new Tffa657PromiseTb4b4b4(Tb4b4b4(Te6edf3resolveTb4b4b4, Te6edf3rejectTb4b4b4) Tb4b4b4=> Tb4b4b4{
Te6edf3existingTb4b4b4.Te6edf3addEventListenerTb4b4b4(Ta5d6ff"load"Tb4b4b4, Tb4b4b4(Tb4b4b4) Tb4b4b4=> Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4, Tb4b4b4{ Te6edf3onceTff7b72: Tff7b72true Tb4b4b4}Tb4b4b4)Tb4b4b4;
Te6edf3existingTb4b4b4.Te6edf3addEventListenerTb4b4b4(Ta5d6ff"error"Tb4b4b4, Tb4b4b4(Tb4b4b4) Tb4b4b4=> Te6edf3rejectTb4b4b4(Tff7b72new Tf85149ErrorTb4b4b4(Ta5d6ff`Ta5d6ffFailed to load Tffd700${Te6edf3srcTffd700}Ta5d6ff`Tb4b4b4)Tb4b4b4)Tb4b4b4, Tb4b4b4{ Te6edf3onceTff7b72: Tff7b72true Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e// Fetch, verify SRI, then inject as blob
Tff7b72const Te6edf3integrity Tff7b72= Tff7b72await Te6edf3loadIntegrityHashesTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72const Te6edf3filename Tff7b72= Te6edf3srcTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff"/"Tb4b4b4)Tb4b4b4.Te6edf3popTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72const Te6edf3expectedHash Tff7b72= Te6edf3integrityTff7b72?Tb4b4b4.Tb4b4b4[Te6edf3filenameTb4b4b4]Tb4b4b4;
Tff7b72const Te6edf3res Tff7b72= Tff7b72await Te6edf3fetchTb4b4b4(Te6edf3srcTb4b4b4)Tb4b4b4;
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3resTb4b4b4.Te6edf3okTb4b4b4) Tb4b4b4{
Tff7b72throw Tff7b72new Tf85149ErrorTb4b4b4(Ta5d6ff`Ta5d6ffCodec2: failed to fetch Tffd700${Te6edf3srcTffd700}Ta5d6ff (Tffd700${Te6edf3resTb4b4b4.Te6edf3statusTffd700}Ta5d6ff)Ta5d6ff`Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72const Te6edf3buf Tff7b72= Tff7b72await Te6edf3resTb4b4b4.Te6edf3arrayBufferTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72await Te6edf3verifySriTb4b4b4(Te6edf3bufTb4b4b4, Te6edf3expectedHashTb4b4b4, Te6edf3filenameTb4b4b4)Tb4b4b4;
T8b949e// Create blob URL for verified content
Tff7b72const Te6edf3blob Tff7b72= Tff7b72new Te6edf3BlobTb4b4b4(Tb4b4b4[Te6edf3bufTb4b4b4]Tb4b4b4, Tb4b4b4{ Te6edf3typeTff7b72: Ta5d6ff"application/javascript" Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tff7b72const Te6edf3blobUrl Tff7b72= Te6edf3URLTb4b4b4.Te6edf3createObjectURLTb4b4b4(Te6edf3blobTb4b4b4)Tb4b4b4;
Tff7b72return Tff7b72new Tffa657PromiseTb4b4b4(Tb4b4b4(Te6edf3resolveTb4b4b4, Te6edf3rejectTb4b4b4) Tb4b4b4=> Tb4b4b4{
Tff7b72const Te6edf3script Tff7b72= Tffa657documentTb4b4b4.Te6edf3createElementTb4b4b4(Ta5d6ff"script"Tb4b4b4)Tb4b4b4;
Te6edf3scriptTb4b4b4.Te6edf3src Tff7b72= Te6edf3blobUrlTb4b4b4;
Te6edf3scriptTb4b4b4.Tff7b72async Tff7b72= Tff7b72falseTb4b4b4;
Te6edf3scriptTb4b4b4.Te6edf3setAttributeTb4b4b4(Te6edf3attrNameTb4b4b4, Te6edf3srcTb4b4b4)Tb4b4b4;
Te6edf3scriptTb4b4b4.Te6edf3addEventListenerTb4b4b4(Ta5d6ff"load"Tb4b4b4, Tb4b4b4(Tb4b4b4) Tb4b4b4=> Tb4b4b4{
Te6edf3URLTb4b4b4.Te6edf3revokeObjectURLTb4b4b4(Te6edf3blobUrlTb4b4b4)Tb4b4b4;
Te6edf3scriptTb4b4b4.Te6edf3setAttributeTb4b4b4(Te6edf3loadedAttrTb4b4b4, Ta5d6ff"true"Tb4b4b4)Tb4b4b4;
Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}Tb4b4b4)Tb4b4b4;
Te6edf3scriptTb4b4b4.Te6edf3addEventListenerTb4b4b4(Ta5d6ff"error"Tb4b4b4, Tb4b4b4(Tb4b4b4) Tb4b4b4=> Tb4b4b4{
Te6edf3URLTb4b4b4.Te6edf3revokeObjectURLTb4b4b4(Te6edf3blobUrlTb4b4b4)Tb4b4b4;
Te6edf3scriptTb4b4b4.Te6edf3removeTb4b4b4(Tb4b4b4)Tb4b4b4;
Te6edf3rejectTb4b4b4(Tff7b72new Tf85149ErrorTb4b4b4(Ta5d6ff`Ta5d6ffFailed to load Tffd700${Te6edf3srcTffd700}Ta5d6ff`Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tffa657documentTb4b4b4.Te6edf3headTb4b4b4.Te6edf3appendChildTb4b4b4(Te6edf3scriptTb4b4b4)Tb4b4b4;
Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72function Te6edf3loadChainTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3codec2ScriptPathsTb4b4b4.Te6edf3reduceTb4b4b4(Tb4b4b4(Te6edf3chainTb4b4b4, Te6edf3srcTb4b4b4) Tb4b4b4=> Te6edf3chainTb4b4b4.Te6edf3thenTb4b4b4(Tb4b4b4(Tb4b4b4) Tb4b4b4=> Te6edf3injectScriptTb4b4b4(Te6edf3srcTb4b4b4)Tb4b4b4)Tb4b4b4, Tffa657PromiseTb4b4b4.Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
T8b949e/**
* Run fn until it succeeds or maxAttempts is reached. Waits between failures with capped exponential backoff.
*
* @param {() => Promise<void>} fn
* @param {{ maxAttempts?: number, baseDelayMs?: number, maxDelayMs?: number }} options
*/
Tff7b72export Tff7b72async Tff7b72function Te6edf3withRetriesTb4b4b4(Te6edf3fnTb4b4b4, Te6edf3options Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4) Tb4b4b4{
Tff7b72const Te6edf3maxAttempts Tff7b72= Te6edf3optionsTb4b4b4.Te6edf3maxAttempts Tff7b72?? T79c0ff12Tb4b4b4;
Tff7b72let Te6edf3delayMs Tff7b72= Te6edf3optionsTb4b4b4.Te6edf3baseDelayMs Tff7b72?? T79c0ff400Tb4b4b4;
Tff7b72const Te6edf3maxDelayMs Tff7b72= Te6edf3optionsTb4b4b4.Te6edf3maxDelayMs Tff7b72?? T79c0ff8000Tb4b4b4;
Tff7b72let Te6edf3lastErrTb4b4b4;
Tff7b72for Tb4b4b4(Tff7b72let Te6edf3attempt Tff7b72= T79c0ff0Tb4b4b4; Te6edf3attempt Tff7b72< Te6edf3maxAttemptsTb4b4b4; Te6edf3attemptTff7b72++Tb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72await Te6edf3fnTb4b4b4(Tb4b4b4)Tb4b4b4;
Tff7b72returnTb4b4b4;
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Te6edf3lastErr Tff7b72= Te6edf3eTb4b4b4;
Tff7b72if Tb4b4b4(Te6edf3attempt Tff7b72=== Te6edf3maxAttempts Tff7b72- T79c0ff1Tb4b4b4) Tb4b4b4{
Tff7b72breakTb4b4b4;
Tb4b4b4}
Tff7b72await Tff7b72new Tffa657PromiseTb4b4b4(Tb4b4b4(Te6edf3rTb4b4b4) Tb4b4b4=> Te6edf3setTimeoutTb4b4b4(Te6edf3rTb4b4b4, Te6edf3delayMsTb4b4b4)Tb4b4b4)Tb4b4b4;
Te6edf3delayMs Tff7b72= Tffa657MathTb4b4b4.Te6edf3minTb4b4b4(Te6edf3maxDelayMsTb4b4b4, Tffa657MathTb4b4b4.Te6edf3floorTb4b4b4(Te6edf3delayMs Tff7b72* T79c0ff1.5Tb4b4b4)Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
Tff7b72throw Te6edf3lastErrTb4b4b4;
Tb4b4b4}
T8b949e/**
* Resolves when all Codec2 helper scripts are present. Rejects if any script fails to load.
* Call from features that require Codec2; use {@link startCodec2ScriptsBackgroundLoad} for startup.
*/
Tff7b72export Tff7b72function Te6edf3ensureCodec2ScriptsLoadedTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72typeof Tffa657window Tff7b72=== Ta5d6ff"undefined"Tb4b4b4) Tb4b4b4{
Tff7b72return Tffa657PromiseTb4b4b4.Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3resolvedOkTb4b4b4) Tb4b4b4{
Tff7b72return Tffa657PromiseTb4b4b4.Te6edf3resolveTb4b4b4(Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3loadPromiseTb4b4b4) Tb4b4b4{
Te6edf3loadPromise Tff7b72= Te6edf3loadChainTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3thenTb4b4b4(Tb4b4b4(Tb4b4b4) Tb4b4b4=> Tb4b4b4{
Te6edf3resolvedOk Tff7b72= Tff7b72trueTb4b4b4;
Tb4b4b4}Tb4b4b4)
Tb4b4b4.Tff7b72catchTb4b4b4(Tb4b4b4(Te6edf3errTb4b4b4) Tb4b4b4=> Tb4b4b4{
Te6edf3loadPromise Tff7b72= Tff7b72nullTb4b4b4;
Tff7b72throw Te6edf3errTb4b4b4;
Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tb4b4b4}
Tff7b72return Te6edf3loadPromiseTb4b4b4;
Tb4b4b4}
T8b949e/**
* Loads Codec2 scripts in the background with retries (embedded server may not be ready on first paint).
* Swallows final failure after logging; the rest of the app stays usable without voice-codec scripts.
*
* @param {{ maxAttempts?: number, baseDelayMs?: number, maxDelayMs?: number }} options
*/
Tff7b72export Tff7b72async Tff7b72function Te6edf3startCodec2ScriptsBackgroundLoadTb4b4b4(Te6edf3options Tff7b72= Tb4b4b4{Tb4b4b4}Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72typeof Tffa657window Tff7b72=== Ta5d6ff"undefined"Tb4b4b4) Tb4b4b4{
Tff7b72returnTb4b4b4;
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72await Te6edf3withRetriesTb4b4b4(Tb4b4b4(Tb4b4b4) Tb4b4b4=> Te6edf3ensureCodec2ScriptsLoadedTb4b4b4(Tb4b4b4)Tb4b4b4, Tb4b4b4{
Te6edf3maxAttemptsTff7b72: Te6edf3optionsTb4b4b4.Te6edf3maxAttempts Tff7b72?? T79c0ff12Tb4b4b4,
Te6edf3baseDelayMsTff7b72: Te6edf3optionsTb4b4b4.Te6edf3baseDelayMs Tff7b72?? T79c0ff400Tb4b4b4,
Te6edf3maxDelayMsTff7b72: Te6edf3optionsTb4b4b4.Te6edf3maxDelayMs Tff7b72?? T79c0ff8000Tb4b4b4,
Tb4b4b4}Tb4b4b4)Tb4b4b4;
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{
Te6edf3consoleTb4b4b4.Te6edf3warnTb4b4b4(Ta5d6ff"Codec2 scripts failed to load after retries; voice codec features may be unavailable."Tb4b4b4, Te6edf3eTb4b4b4)Tb4b4b4;
Tb4b4b4}
Tb4b4b4}
T8b949e/** Clears loader state (for unit tests only). */
Tff7b72export Tff7b72function Te6edf3resetCodec2LoaderStateTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3resolvedOk Tff7b72= Tff7b72falseTb4b4b4;
Te6edf3loadPromise Tff7b72= Tff7b72nullTb4b4b4;
Tb4b4b4}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────